-- https://source.android.google.cn/docs/core/connect/bluetooth/hci_requirements


do
    local apcf_opcodes = {
        [0x00] = "APCF Enable",
        [0x01] = "APCF Set Filtering parameters",
        [0x02] = "APCF Broadcaster Address",
        [0x03] = "APCF Service UUID",
        [0x04] = "APCF Service Solicitation UUID",
        [0x05] = "APCF Local Name",
        [0x06] = "APCF Manufacturer Data",
        [0x07] = "APCF Service Data",
        [0x08] = "APCF Transport Discovery Service",
        [0x09] = "APCF AD Type Filter",
        [0x10] = "0x10 ~ 0xAF - Reserved for future use",
        [0xB0] = "0xB0 ~ 0xDF - Reserved for vendor",
        [0xE0] = "0xE0 ~ 0xFE - Reserved for future use",
        [0xFF] = "APCF Read extended Features",
    }

    local apcf_actions = {
        [0x00] = "Add",
        [0x01] = "Delete",
        [0x02] = "Clear",
    }

    local apcf_logic_type = {
        [0] = "OR",
        [1] = "AND",
    }

    local apcf_set_filter_param_delivery_mode = {
        [0x00] = "immediate",
        [0x01] = "on_found",
        [0x02] = "batched",
    }

    local apcf_address_type = {
        [0x00] = "Public",
        [0x01] = "Random",
        [0x02] = "NA (ignore the address type)",
    }

    local p_vendor = Proto("btvendor", "test vendor ocf data")
    local p_opcode = Proto("btvendor.opcode","Opcode")
    local f_ogf = ProtoField.uint16("btvendor.opcode.ogf", "Opcode Group", base.HEX, nil, 0xFC00)
    local f_ocf = ProtoField.uint16("btvendor.opcode.ocf", "Opcode Command", base.HEX, nil, 0x03FF)
    local f_len = ProtoField.uint8("btvendor.len", "Length", base.DEC)

    local p_apcf = Proto("btvendor.apcf","APCF Data")
    local f_apcf_opcode = ProtoField.uint8("btvendor.apcf.opcode", "APCF Opcode", base.DEC, apcf_opcodes)
    local f_apcf_enable = ProtoField.uint8("btvendor.apcf.enable", "Enable", base.DEC, {"Disable APCF Feature", "Enable APCF Feature"})

    local p_apcf_set_filter_param = Proto("btvendor.apcf.set_filter", "APCF Set Filter Parameter")
    local f_apcf_set_filter_param_action = ProtoField.uint8("btvendor.apcf.set_filter.action", "Action", base.DEC, apcf_actions)
    local f_apcf_set_filter_param_index = ProtoField.uint8("btvendor.apcf.set_filter.index", "Filter Index", base.DEC)
    local p_apcf_set_filter_param_feature = Proto("btvendor.apcf.set_filter.feature", "Feature Selection")
    local f_apcf_set_filter_param_bdf = ProtoField.uint16("btvendor.apcf.set_filter.feature.broadcase_address_filter", "Enable Broadcase Address Filter", base.DEC, nil, 0x8000)
    local f_apcf_set_filter_param_sdf = ProtoField.uint16("btvendor.apcf.set_filter.feature.service_data_change_filter", "Enable Service Data Change Filter", base.DEC, nil, 0x4000)
    local f_apcf_set_filter_param_suc = ProtoField.uint16("btvendor.apcf.set_filter.feature.service_uuid_check", "Enable Service UUID Check", base.DEC, nil, 0x2000)
    local f_apcf_set_filter_param_ssuc = ProtoField.uint16("btvendor.apcf.set_filter.feature.service_solicitation_uuid_check", "Enable Service Solicitation UUID Check", base.DEC, nil, 0x1000)
    local f_apcf_set_filter_param_lnc = ProtoField.uint16("btvendor.apcf.set_filter.feature.local_name_check", "Enable Local Name Check", base.DEC, nil, 0x0800)
    local f_apcf_set_filter_param_mdc = ProtoField.uint16("btvendor.apcf.set_filter.feature.manufacturer_data_check", "Enable Manufacturer Data Check", base.DEC, nil, 0x0400)
    local f_apcf_set_filter_param_sdc = ProtoField.uint16("btvendor.apcf.set_filter.feature.service_data_check", "Enable Service Data Check", base.DEC, nil, 0x0200)
    local f_apcf_set_filter_param_tdsc = ProtoField.uint16("btvendor.apcf.set_filter.feature.transport_discovery_service_check", "Enable Transport Discovery Service Check", base.DEC, nil, 0x0100)
    local f_apcf_set_filter_param_atc = ProtoField.uint16("btvendor.apcf.set_filter.feature.ad_type_check", "Enable AD Type Check", base.DEC, nil, 0x0080)

    local p_apcf_set_filter_param_feature_logic = Proto("btvendor.apcf.set_filter.feature_logic", "Feature Selection List Logic")
    local f_apcf_set_filter_param_bdf_logic = ProtoField.uint16("btvendor.apcf.set_filter.feature_logic.broadcase_address_filter", "Enable Broadcase Address Filter Logic", base.DEC, apcf_logic_type, 0x8000)
    local f_apcf_set_filter_param_sdf_logic = ProtoField.uint16("btvendor.apcf.set_filter.feature_logic.service_data_change_filter", "Enable Service Data Change Filter Logic", base.DEC, apcf_logic_type, 0x4000)
    local f_apcf_set_filter_param_suc_logic = ProtoField.uint16("btvendor.apcf.set_filter.feature_logic.service_uuid_check", "Enable Service UUID Check Logic", base.DEC, apcf_logic_type, 0x2000)
    local f_apcf_set_filter_param_ssuc_logic = ProtoField.uint16("btvendor.apcf.set_filter.feature_logic.service_solicitation_uuid_check", "Enable Service Solicitation UUID Check Logic", base.DEC, apcf_logic_type, 0x1000)
    local f_apcf_set_filter_param_lnc_logic = ProtoField.uint16("btvendor.apcf.set_filter.feature_logic.local_name_check", "Enable Local Name Check Logic", base.DEC, apcf_logic_type, 0x0800)
    local f_apcf_set_filter_param_mdc_logic = ProtoField.uint16("btvendor.apcf.set_filter.feature_logic.manufacturer_data_check", "Enable Manufacturer Data Check Logic", base.DEC, apcf_logic_type, 0x0400)
    local f_apcf_set_filter_param_sdc_logic = ProtoField.uint16("btvendor.apcf.set_filter.feature_logic.service_data_check", "Enable Service Data Check Logic", base.DEC, apcf_logic_type, 0x0200)
    local f_apcf_set_filter_param_tdsc_logic = ProtoField.uint16("btvendor.apcf.set_filter.feature_logic.transport_discovery_service_check", "Enable Transport Discovery Service Check Logic", base.DEC, apcf_logic_type, 0x0100)
    local f_apcf_set_filter_param_atc_logic = ProtoField.uint16("btvendor.apcf.set_filter.feature_logic.ad_type_check", "Enable AD Type Check Logic", base.DEC, apcf_logic_type, 0x0080)

    local f_apcf_filter_logic_type = ProtoField.uint8("btvendor.apcf.set_filter.filter_logic", "Filter Logic Type", base.DEC, apcf_logic_type)
    local f_apcf_rssi_high_thresh = ProtoField.int8("btvendor.apcf.set_filter.rssi_high_thresh", "Rssi High Thresh", base.DEC)
    local f_apcf_delivery_mode = ProtoField.uint8("btvendor.apcf.set_filter.delivery_mode", "Delivery Mode", base.DEC, apcf_set_filter_param_delivery_mode)
    local f_apcf_onfound_timeout = ProtoField.uint16("btvendor.apcf.set_filter.onfound_timeout", "OnFound Timeout(ms)", base.DEC)
    local f_apcf_onfound_timeout_cnt = ProtoField.uint8("btvendor.apcf.set_filter.onfound_timeout_cnt", "OnFound Timeout Count", base.DEC)
    local f_apcf_rssi_low_thresh = ProtoField.int8("btvendor.apcf.set_filter.rssi_low_thresh", "Rssi Low Thresh", base.DEC)
    local f_apcf_onlost_timeout = ProtoField.uint16("btvendor.apcf.set_filter.onlost_timeout", "OnLost Timeout(ms)", base.DEC)
    local f_apcf_num_of_tracking_entries = ProtoField.uint16("btvendor.apcf.set_filter.num_of_tracking_entries", "Number Of Tracking Entries", base.DEC)

    local f_apcf_filter_action = ProtoField.uint8("btvendor.apcf.action", "Action", base.DEC, apcf_actions)
    local f_apcf_filter_index = ProtoField.uint8("btvendor.apcf.index", "Filter Index", base.DEC)
    local f_apcf_filter_data = ProtoField.bytes("btvendor.apcf.data", "Data", base.NONE)
    local f_apcf_filter_mask = ProtoField.bytes("btvendor.apcf.mask", "Mask", base.NONE)
    local f_apcf_filter_address = ProtoField.ether("btvendor.apcf.addr", "Address")
    local f_apcf_filter_address_type = ProtoField.uint8("btvendor.apcf.addr_type", "Address Type", base.DEC, apcf_address_type)

    p_vendor.fields = {
        f_len,
    }

    p_opcode.fields = {
        f_ogf,
        f_ocf,
    }

    p_apcf.fields = {
        f_apcf_opcode,
        f_apcf_enable,
        f_apcf_filter_action,
        f_apcf_filter_index,
        f_apcf_filter_data,
        f_apcf_filter_mask,
        f_apcf_filter_address,
        f_apcf_filter_address_type,
    }

    p_apcf_set_filter_param.fields = {
        f_apcf_set_filter_param_action,
        f_apcf_set_filter_param_index,
        f_apcf_filter_logic_type,
        f_apcf_rssi_high_thresh,
        f_apcf_delivery_mode,
        f_apcf_onfound_timeout,
        f_apcf_onfound_timeout_cnt,
        f_apcf_rssi_low_thresh,
        f_apcf_onlost_timeout,
        f_apcf_num_of_tracking_entries,
    }

    p_apcf_set_filter_param_feature.fields = {
        f_apcf_set_filter_param_bdf,
        f_apcf_set_filter_param_sdf,
        f_apcf_set_filter_param_suc,
        f_apcf_set_filter_param_ssuc,
        f_apcf_set_filter_param_lnc,
        f_apcf_set_filter_param_mdc,
        f_apcf_set_filter_param_sdc,
        f_apcf_set_filter_param_tdsc,
        f_apcf_set_filter_param_atc,
    }

    p_apcf_set_filter_param_feature_logic.fields = {
        f_apcf_set_filter_param_bdf_logic,
        f_apcf_set_filter_param_sdf_logic,
        f_apcf_set_filter_param_suc_logic,
        f_apcf_set_filter_param_ssuc_logic,
        f_apcf_set_filter_param_lnc_logic,
        f_apcf_set_filter_param_mdc_logic,
        f_apcf_set_filter_param_sdc_logic,
        f_apcf_set_filter_param_tdsc_logic,
        f_apcf_set_filter_param_atc_logic,
    }

    local function get_action_index_str(orig_info, action, index)
        return string.format("%s (%s %d)", orig_info, apcf_actions[action], index)
    end

    local function apcf_cmd_set_filtering_parameters_sub_cmd(buf, pkt, tree)
        local t = tree:add(p_apcf_set_filter_param, buf())
        local offset = 0
        local action = buf(offset, 1)
        tree:add(f_apcf_filter_action, action)
        offset = offset + 1
        local index = buf(offset, 1)
        tree:add(f_apcf_filter_index, index)
        offset = offset + 1

        pkt.cols.info:set(get_action_index_str(pkt.cols.info, action:uint(), index:uint()))

        if buf(offset):len() == 0 then
            return
        end

        local feature = buf(offset, 2)
        offset = offset + 2
        local ft = t:add(p_apcf_set_filter_param_feature, feature)
        ft:add(f_apcf_set_filter_param_bdf, feature)
        ft:add(f_apcf_set_filter_param_sdf, feature)
        ft:add(f_apcf_set_filter_param_suc, feature)
        ft:add(f_apcf_set_filter_param_ssuc, feature)
        ft:add(f_apcf_set_filter_param_lnc, feature)
        ft:add(f_apcf_set_filter_param_mdc, feature)
        ft:add(f_apcf_set_filter_param_sdc, feature)
        ft:add(f_apcf_set_filter_param_tdsc, feature)
        ft:add(f_apcf_set_filter_param_atc, feature)
        local feature_logic = buf(offset, 2)
        offset = offset + 2
        local ft = t:add(p_apcf_set_filter_param_feature_logic, feature_logic)
        ft:add(f_apcf_set_filter_param_bdf_logic, feature_logic)
        ft:add(f_apcf_set_filter_param_sdf_logic, feature_logic)
        ft:add(f_apcf_set_filter_param_suc_logic, feature_logic)
        ft:add(f_apcf_set_filter_param_ssuc_logic, feature_logic)
        ft:add(f_apcf_set_filter_param_lnc_logic, feature_logic)
        ft:add(f_apcf_set_filter_param_mdc_logic, feature_logic)
        ft:add(f_apcf_set_filter_param_sdc_logic, feature_logic)
        ft:add(f_apcf_set_filter_param_tdsc_logic, feature_logic)
        ft:add(f_apcf_set_filter_param_atc_logic, feature_logic)

        t:add(f_apcf_filter_logic_type, buf(offset, 1))
        offset = offset + 1
        t:add(f_apcf_rssi_high_thresh, buf(offset, 1))
        offset = offset + 1
        t:add(f_apcf_delivery_mode, buf(offset, 1))
        offset = offset + 1
        t:add(f_apcf_onfound_timeout, buf(offset, 2))
        offset = offset + 2
        t:add(f_apcf_onfound_timeout_cnt, buf(offset, 1))
        offset = offset + 1
        t:add(f_apcf_rssi_low_thresh, buf(offset, 1))
        offset = offset + 1
        t:add(f_apcf_onlost_timeout, buf(offset, 2))
        offset = offset + 2
        t:add(f_apcf_num_of_tracking_entries, buf(offset, 2))
        offset = offset + 2
    end

    local function apcf_cmd_broadcast_address_sub_cmd(buf, pkt, tree)
        local offset = 0
        local action = buf(offset, 1)
        tree:add(f_apcf_filter_action, action)
        offset = offset + 1
        local index = buf(offset, 1)
        tree:add(f_apcf_filter_index, index)
        offset = offset + 1

        pkt.cols.info:set(get_action_index_str(pkt.cols.info, action:uint(), index:uint()))

        tree:add(f_apcf_filter_address, buf(offset, 6))
        offset = offset + 6
        tree:add(f_apcf_filter_address_type, buf(offset, 1))
        offset = offset + 1
    end

    local function apcf_cmd_data_mask_cmd(buf, pkt, tree)
        local offset = 0
        local action = buf(offset, 1)
        tree:add(f_apcf_filter_action, action)
        offset = offset + 1
        local index = buf(offset, 1)
        tree:add(f_apcf_filter_index, index)
        offset = offset + 1

        pkt.cols.info:set(get_action_index_str(pkt.cols.info, action:uint(), index:uint()))

        local data_size = buf(offset):len() / 2
        tree:add(f_apcf_filter_data, buf(offset, data_size))
        offset = offset + data_size
        tree:add(f_apcf_filter_mask, buf(offset, data_size))
        offset = offset + data_size
    end

    local function apcf_command(buf, pkt, tree)
        pkt.cols.protocol:set("APCF")
        local t = tree:add(p_apcf, buf(), "APCF Data")
        local offset = 0
        local apcf_opcode = buf(offset, 1)
        t:add(f_apcf_opcode, apcf_opcode)
        offset = offset + 1

        if apcf_opcode:uint() < 0x10 or apcf_opcode:uint() == 0xFF then
            pkt.cols.info:set(apcf_opcodes[apcf_opcode:uint()])
        end

        if buf(offset):len() == 0 then
            return
        end

        if apcf_opcode:uint() == 0x0 then
            t:add(f_apcf_enable, buf(offset, 1))
            offset = offset + 1
        elseif apcf_opcode:uint() == 0x1 then
            apcf_cmd_set_filtering_parameters_sub_cmd(buf(offset):tvb(), pkt, t)
        elseif apcf_opcode:uint() == 0x2 then
        elseif apcf_opcode:uint() == 0x6 then
            apcf_cmd_data_mask_cmd(buf(offset):tvb(), pkt, t)
        elseif apcf_opcode:uint() == 0x7 then
            apcf_cmd_data_mask_cmd(buf(offset):tvb(), pkt, t)
        end
    end

    local function vendor_decode(buf, pkt, tree)
        if buf(0, 1):uint() == 0x0e then
            -- vendor event
            return false
        end

        local t = tree:add(p_vendor, buf(), "Vendor Command Data")
        local offset = 0
        local opcode = buf(offset, 2)
        offset = offset + 2

        local subtree = t:add_le(p_opcode, opcode)
        subtree:add_le(f_ogf, opcode)
        subtree:add_le(f_ocf, opcode)
        local ocf = bit32.band(opcode:le_uint(), 0x3FF)
        -- print(string.format("bitfield ocf=%X", ocf))

        t:add(f_len, buf(offset, 1))
        offset = offset + 1

        if ocf == 0x157 then
            apcf_command(buf(offset):tvb(), pkt, t)
        end

        return true
    end

    local data_dis = Dissector.get("data")
    function p_vendor.dissector(buf, pkt, tree)
        if vendor_decode(buf, pkt, tree) then
            -- Decode data
        else
            -- already decode as vendor command
            -- data_dis:call(buf, pkt, tree)
        end
    end

    -- local ocf_dis = DissectorTable.get("bthci_vendor.cmd.ocf")
    -- if (ocf_dis ~= nil) then
    --     print("got bthci_vendor.cmd.ocf")
    --     -- ocf_dis:add(0x157, p_vendor)
    -- end

    local vendor = DissectorTable.get("bthci_cmd.vendor")
    if (vendor ~= nil) then
        vendor:add_for_decode_as(p_vendor)
    end

end